from functools import partial
from itertools import chain, repeat
from operator import or_

__all__ = ["partial","ego", "compose", "remove_if", "foldr", "xfoldr", "map0_n", "map1_n", "empty_lists", "listor_", "flatten", "flatten_deep", "which", "subdict", "set_from_dict"]

# the identity function
ego = lambda x:x

# function composition
def compose(func, curried): return lambda *args, **kwargs: func(curried(*args, **kwargs))

# remove_if
remove_if = lambda f, l: filter(lambda x: not(f(x)), l)

# foldr
foldr = lambda f,i,s: reduce(f, s, i)

# foldr factory
xfoldr = lambda f,i: lambda s: reduce(f, s, i)

# map f to all naturals up to n-1 (zero-based)
map0_n = lambda f,n: map(f, range(n))

# map f to all naturals up to n (one-based)
map1_n = lambda f,n: map(f, range(1,n+1))

# make a list of N empty lists
empty_lists = partial(repeat, [])

# reduce a list using the 'or' operator
listor_ = partial(reduce, or_)

# flatten a list of lists (shallow)
flatten = compose(list, partial(apply, chain))

def flatten_deep(x):
    """ Flattens a list of lists (deep) """
    result = []
    for el in x:
        if hasattr(el, "__iter__") and not isinstance(el, basestring):
            result.extend(flatten_deep(el))
        else:
            result.append(el)
    return result

def which(p,l):
    """ Returns the index of the first element in l which satisfies p """
    try:
        return filter(partial(lambda g,t: g(t[1]), p), enumerate(l))[0][0]
    except IndexError:
        return -1

subdict = lambda d,l: dict((k,v) for k,v in d.items() if k in l)
set_from_dict = lambda i,d,l: [setattr(i, k, v) for k,v in d.items() if k in l]
